<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>小智服务器测试页面</title>np
    <link rel="stylesheet" href="test_page.css">
    <style>
        #fileProtocolWarning {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.8);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 9999;
            color: white;
            padding: 20px;
            box-sizing: border-box;
        }

        #fileProtocolWarning h2 {
            color: #ff4d4d;
            margin-bottom: 20px;
        }

        #fileProtocolWarning pre {
            background-color: green;
            font-size: 18px;
            padding: 15px;
            border-radius: 5px;
            font-family: monospace;
            overflow-x: auto;
            margin: 15px 0;
        }

        #fileProtocolWarning button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 10px 20px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 10px 2px;
            cursor: pointer;
            border-radius: 4px;
        }

        #fileProtocolWarning button:hover {
            background-color: #45a049;
        }

        /* MCP 工具管理样式 */
        .mcp-tools-container {
            display: grid;
            gap: 12px;
            margin-top: 10px;
        }

        .mcp-tool-card {
            background-color: white;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            padding: 15px;
            transition: all 0.2s;
        }

        .mcp-tool-card:hover {
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }

        .mcp-tool-card.disabled {
            opacity: 0.6;
            pointer-events: none;
        }

        .mcp-tool-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 10px;
        }

        .mcp-tool-name {
            font-size: 15px;
            font-weight: 600;
            color: #333;
            flex: 1;
        }

        .mcp-tool-actions {
            display: flex;
            gap: 6px;
        }

        .mcp-tool-description {
            color: #666;
            font-size: 13px;
            line-height: 1.5;
            margin-bottom: 8px;
        }

        .mcp-tool-info {
            background-color: #f9f9f9;
            border: 1px solid #e0e0e0;
            border-radius: 4px;
            padding: 8px;
            font-size: 12px;
        }

        .mcp-tool-info-row {
            display: flex;
            gap: 15px;
            margin-bottom: 4px;
        }

        .mcp-tool-info-label {
            color: #999;
            min-width: 60px;
        }

        .mcp-tool-info-value {
            color: #333;
            font-family: 'Courier New', monospace;
        }

        .mcp-property-item {
            background-color: white;
            border: 1px solid #e0e0e0;
            border-radius: 5px;
            padding: 12px;
            margin-bottom: 10px;
        }

        .mcp-property-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 10px;
        }

        .mcp-property-name {
            font-weight: 600;
            color: #333;
        }

        .mcp-property-row {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
            margin-bottom: 8px;
        }

        .mcp-property-row-full {
            margin-bottom: 8px;
        }

        .mcp-small-label {
            display: block;
            margin-bottom: 4px;
            font-size: 12px;
            color: #666;
        }

        .mcp-small-input {
            width: 100%;
            padding: 6px 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 13px;
        }

        .mcp-checkbox-label {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 13px;
            color: #666;
            cursor: pointer;
        }

        .mcp-error {
            background-color: #ffebee;
            color: #c62828;
            padding: 10px;
            border-radius: 5px;
            margin-bottom: 15px;
            font-size: 14px;
        }

        .mcp-badge {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 12px;
            font-size: 11px;
            font-weight: 500;
            margin-left: 8px;
        }

        .mcp-badge-required {
            background-color: #ffebee;
            color: #c62828;
        }

        .mcp-badge-optional {
            background-color: #e3f2fd;
            color: #1976d2;
        }
    </style>
    <script>
        // 检测是否使用file://协议打开
        if (window.location.protocol === 'file:') {
            document.addEventListener('DOMContentLoaded', function () {
                // 创建警告框
                const warningDiv = document.createElement('div');
                warningDiv.id = 'fileProtocolWarning';
                warningDiv.innerHTML = `
                    <h2>⚠️ 警告：请使用HTTP服务器打开此页面</h2>
                    <p>您当前使用的是本地文件方式打开页面（file://协议），这可能导致页面功能异常。</p>
                    <p>您可以使用nginx映射启动测试页面，也可以请按照以下步骤使用python启动测试http服务：</p>
                    <ol>
                        <li>打开命令行终端</li>
                        <li>命令行进入到 xiaozhi-server/test 目录</li>
                        <li>执行以下命令启动HTTP服务器：</li>
                    </ol>
                    <pre>python -m http.server 8006</pre>
                    <p>然后在浏览器中访问：<strong>http://localhost:8006/test_page.html</strong></p>
                `;
                document.body.appendChild(warningDiv);
            });
        }
    </script>
</head>

<body>
    <div class="container">
        <h1>小智服务器测试页面</h1>

        <div id="scriptStatus" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);">
            正在加载Opus库...
        </div>

        <!-- 添加配置面板 -->
        <div class="section">
            <h2>
                设备配置
                <span class="device-info">
                    <span>MAC: <strong id="displayMac"></strong></span>
                    <span>客户端: <strong id="displayClient">web_test_client</strong></span>
                </span>
                <button class="toggle-button" id="toggleConfig">编辑</button>
            </h2>
            <div class="config-panel" id="configPanel">
                <div class="control-panel">
                    <div class="config-item">
                        <label for="deviceMac">设备MAC:</label>
                        <input type="text" id="deviceMac" placeholder="设备MAC地址">
                    </div>
                    <div class="config-item">
                        <label for="deviceName">设备名称:</label>
                        <input type="text" id="deviceName" value="Web测试设备" placeholder="设备名称">
                    </div>
                    <div class="config-item">
                        <label for="clientId">客户端ID:</label>
                        <input type="text" id="clientId" value="web_test_client" placeholder="客户端ID">
                    </div>
                    <div class="config-item">
                        <label for="token">认证Token:</label>
                        <input type="text" id="token" value="your-token1" placeholder="认证Token">
                    </div>
                </div>
            </div>
        </div>

        <div class="section">
            <h2>
                连接信息
                <span class="connection-status">
                    <span>OTA: <span id="otaStatus" class="status">ota未连接</span></span>
                    <span>WS: <span id="connectionStatus" class="status">ws未连接</span></span>
                </span>
            </h2>
            <div class="connection-controls">
                <input type="text" id="otaUrl" value="http://127.0.0.1:8002/xiaozhi/ota/"
                    placeholder="OTA服务器地址，如：http://127.0.0.1:8002/xiaozhi/ota/" />
                <input type="text" id="serverUrl" value="" readonly disabled placeholder="点击连接按钮后，自动从OTA接口获取" />
                <button id="connectButton">连接</button>
            </div>
        </div>

        <div class="section">
            <div class="tabs">
                <button class="tab active" data-tab="text">文本消息</button>
                <button class="tab" data-tab="voice">语音消息</button>
            </div>

            <div class="tab-content active" id="textTab">
                <div class="message-input">
                    <input type="text" id="messageInput" placeholder="输入消息..." disabled>
                    <button id="sendTextButton" disabled>发送</button>
                </div>
            </div>

            <div class="tab-content" id="voiceTab">
                <div class="audio-controls">
                    <button id="recordButton" class="record-button" disabled>开始录音</button>
                </div>
                <canvas id="audioVisualizer" class="audio-visualizer"></canvas>
            </div>
        </div>

        <div class="section">
            <h2>会话记录</h2>
            <div class="flex-container">
                <div id="conversation" class="conversation"></div>
                <div id="logContainer">
                    <div class="log-entry log-info">准备就绪，请连接服务器开始测试...</div>
                </div>
            </div>
        </div>

        <!-- MCP 工具管理区域 -->
        <div class="section">
            <h2>
                MCP 工具管理
                <span class="connection-status">
                    <span id="mcpToolsCount">0 个工具</span>
                </span>
                <button class="toggle-button" id="toggleMcpTools">展开</button>
            </h2>
            <div class="config-panel" id="mcpToolsPanel">
                <div id="mcpToolsContainer" class="mcp-tools-container">
                    <!-- 工具列表将动态插入这里 -->
                </div>
                <div style="text-align: center; padding: 15px 0;">
                    <button class="btn" id="addMcpToolBtn" style="background-color: #4caf50;">
                        ➕ 添加新工具
                    </button>
                </div>
            </div>
        </div>

        <!-- MCP 工具编辑模态框 -->
        <div id="mcpToolModal"
            style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; overflow-y: auto;">
            <div
                style="background-color: white; border-radius: 10px; padding: 25px; width: 90%; max-width: 700px; margin: 50px auto; max-height: 85vh; overflow-y: auto;">
                <div
                    style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #e0e0e0;">
                    <h2 id="mcpModalTitle" style="font-size: 20px; font-weight: bold; color: #333; margin: 0;">添加工具</h2>
                    <button id="closeMcpModalBtn"
                        style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px;">&times;</button>
                </div>

                <div id="mcpErrorContainer"></div>

                <form id="mcpToolForm">
                    <div style="margin-bottom: 20px;">
                        <label
                            style="display: block; margin-bottom: 8px; font-weight: 500; color: #333; font-size: 14px;">工具名称
                            *</label>
                        <input type="text" id="mcpToolName" placeholder="例如: self.get_device_status" required
                            style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px;">
                    </div>

                    <div style="margin-bottom: 20px;">
                        <label
                            style="display: block; margin-bottom: 8px; font-weight: 500; color: #333; font-size: 14px;">工具描述
                            *</label>
                        <textarea id="mcpToolDescription" placeholder="详细描述工具的功能和使用场景..." required
                            style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; min-height: 80px; resize: vertical;"></textarea>
                    </div>

                    <div style="margin-bottom: 20px;">
                        <label
                            style="display: block; margin-bottom: 8px; font-weight: 500; color: #333; font-size: 14px;">输入参数</label>
                        <div
                            style="background-color: #f9f9f9; border: 1px solid #ddd; border-radius: 5px; padding: 15px;">
                            <div id="mcpPropertiesContainer">
                                <div style="text-align: center; padding: 20px; color: #999; font-size: 14px;">
                                    暂无参数，点击下方按钮添加参数</div>
                            </div>
                            <button type="button" id="addMcpPropertyBtn"
                                style="width: 100%; margin-top: 10px; padding: 8px 15px; border: none; border-radius: 5px; background-color: #2196f3; color: white; cursor: pointer; font-size: 14px;">
                                ➕ 添加参数
                            </button>
                        </div>
                    </div>

                    <div style="margin-bottom: 20px;">
                        <label
                            style="display: block; margin-bottom: 8px; font-weight: 500; color: #333; font-size: 14px;">
                            模拟返回结果 (JSON 格式，可选)
                            <span style="font-size: 12px; color: #999; font-weight: normal;">- 留空则返回默认成功消息</span>
                        </label>
                        <textarea id="mcpMockResponse" placeholder='{"success": true, "data": "执行成功"}'
                            style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 13px; font-family: 'Courier New', monospace; min-height: 100px; resize: vertical;"></textarea>
                        <div style="font-size: 12px; color: #666; margin-top: 4px;">
                            💡 提示：如果设置了模拟返回结果，工具调用时将返回这个 JSON 对象
                        </div>
                    </div>

                    <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 25px;">
                        <button type="button" id="cancelMcpBtn"
                            style="padding: 8px 15px; border: none; border-radius: 5px; background-color: #9e9e9e; color: white; cursor: pointer; font-size: 14px;">取消</button>
                        <button type="submit"
                            style="padding: 8px 15px; border: none; border-radius: 5px; background-color: #4caf50; color: white; cursor: pointer; font-size: 14px;">保存</button>
                    </div>
                </form>
            </div>
        </div>
    </div>

    <!-- Opus解码库 -->
    <script src="libopus.js"></script>

    <script type="module">
        import { log } from './js/utils/logger.js';
        import { webSocketConnect } from './js/xiaoZhiConnect.js';
        import { checkOpusLoaded, initOpusEncoder } from './js/opus.js';
        import { addMessage } from './js/document.js'
        import BlockingQueue from './js/utils/BlockingQueue.js'
        import { createStreamingContext } from './js/StreamingContext.js'
        // 需要加载的脚本列表 - 移除Opus依赖
        const scriptFiles = [];

        // 脚本加载状态
        const scriptStatus = {
            loading: 0,
            loaded: 0,
            failed: 0,
            total: scriptFiles.length
        };

        // 全局变量
        let websocket = null;
        let mediaRecorder = null;
        let audioContext = null;
        let analyser = null;
        let audioChunks = [];
        let isRecording = false;
        let visualizerCanvas = document.getElementById('audioVisualizer');
        let visualizerContext = visualizerCanvas.getContext('2d');
        let audioQueue = [];
        let isPlaying = false;
        let opusDecoder = null; // Opus解码器
        let visualizationRequest = null; // 动画帧请求ID

        // 音频流缓冲相关
        let audioBuffers = []; // 用于存储接收到的所有音频数据
        let totalAudioSize = 0; // 跟踪累积的音频大小

        let audioBufferQueue = [];     // 存储接收到的音频包
        let isAudioPlaying = false;    // 是否正在播放音频
        const BUFFER_THRESHOLD = 3;    // 缓冲包数量阈值，至少累积3个包再开始播放
        const MIN_AUDIO_DURATION = 0.3; // 最小音频长度(秒)，小于这个长度的音频会被合并
        let streamingContext = null;   // 音频流上下文
        const SAMPLE_RATE = 16000;     // 采样率
        const CHANNELS = 1;            // 声道数
        const FRAME_SIZE = 960;        // 帧大小

        // DOM元素
        const connectButton = document.getElementById('connectButton');
        const serverUrlInput = document.getElementById('serverUrl');
        const connectionStatus = document.getElementById('connectionStatus');
        const messageInput = document.getElementById('messageInput');
        const sendTextButton = document.getElementById('sendTextButton');
        const recordButton = document.getElementById('recordButton');
        const stopButton = document.getElementById('stopButton');
        const conversationDiv = document.getElementById('conversation');
        const logContainer = document.getElementById('logContainer');

        function getAudioContextInstance() {
            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)({
                    sampleRate: SAMPLE_RATE,
                    latencyHint: 'interactive'
                });
                log('创建音频上下文，采样率: ' + SAMPLE_RATE + 'Hz', 'debug');
            }
            return audioContext;
        }

        // 初始化可视化器
        function initVisualizer() {
            visualizerCanvas.width = visualizerCanvas.clientWidth;
            visualizerCanvas.height = visualizerCanvas.clientHeight;
            visualizerContext.fillStyle = '#fafafa';
            visualizerContext.fillRect(0, 0, visualizerCanvas.width, visualizerCanvas.height);
        }

        // 绘制音频可视化效果
        function drawVisualizer(dataArray) {
            visualizationRequest = requestAnimationFrame(() => drawVisualizer(dataArray));

            if (!isRecording) return;

            analyser.getByteFrequencyData(dataArray);

            visualizerContext.fillStyle = '#fafafa';
            visualizerContext.fillRect(0, 0, visualizerCanvas.width, visualizerCanvas.height);

            const barWidth = (visualizerCanvas.width / dataArray.length) * 2.5;
            let barHeight;
            let x = 0;

            for (let i = 0; i < dataArray.length; i++) {
                barHeight = dataArray[i] / 2;

                visualizerContext.fillStyle = `rgb(${barHeight + 100}, 50, 50)`;
                visualizerContext.fillRect(x, visualizerCanvas.height - barHeight, barWidth, barHeight);

                x += barWidth + 1;
            }
        }

        const queue = new BlockingQueue();

        // 启动缓存进程
        async function startAudioBuffering() {
            log("开始音频缓冲...", 'info');

            // 先尝试初始化解码器，以便在播放时已准备好
            initOpusDecoder().catch(error => {
                log(`预初始化Opus解码器失败: ${error.message}`, 'warning');
                // 继续缓冲，我们会在播放时再次尝试初始化
            });
            const timeout = 1000;
            while (true) {
                // 每次数据空的时候等三条数据
                const packets = await queue.dequeue(
                    10,                       // 至少 3 条
                    timeout,                     // 最多等 300 ms
                    (count) => {             // 超时额外回调
                        log(`缓冲超时，当前缓冲包数: ${count}，开始播放`, 'info');
                    }
                );
                if (packets.length) {
                    log(`已缓冲 ${packets.length} 个音频包，开始播放`, 'info');
                    streamingContext.pushAudioBuffer(packets)
                }
                // 50毫秒里，有多少给多少
                while (true) {
                    const data = await queue.dequeue(99, 50)
                    if (data.length) {
                        streamingContext.pushAudioBuffer(data)
                    } else {
                        break
                    }
                }
            }
        }

        // 播放已缓冲的音频
        async function playBufferedAudio() {
            // 确保Opus解码器已初始化
            try {
                // 确保音频上下文存在
                audioContext = getAudioContextInstance();

                // 确保解码器已初始化
                if (!opusDecoder) {
                    log('初始化Opus解码器...', 'info');
                    try {
                        opusDecoder = await initOpusDecoder();
                        if (!opusDecoder) {
                            throw new Error('解码器初始化失败');
                        }
                        log('Opus解码器初始化成功', 'success');
                    } catch (error) {
                        log('Opus解码器初始化失败: ' + error.message, 'error');
                        isAudioPlaying = false;
                        return;
                    }
                }

                // 创建流式播放上下文
                if (!streamingContext) {
                    streamingContext = createStreamingContext(opusDecoder, audioContext, SAMPLE_RATE, CHANNELS, MIN_AUDIO_DURATION);
                }
                // 取出 packets -> 调用 streamingContext.pushAudioBuffer(packets) -> 存入 streamingContext.audioBufferQueue
                // streamingContext.decodeOpusFrames() 从 audioBufferQueue 获取到数据并解码播放
                streamingContext.decodeOpusFrames();
                streamingContext.startPlaying();

            } catch (error) {
                log(`播放已缓冲的音频出错: ${error.message}`, 'error');
                isAudioPlaying = false;
                streamingContext = null;
            }
        }



        // 初始化Opus解码器 - 确保完全初始化完成后才返回
        async function initOpusDecoder() {
            if (opusDecoder) return opusDecoder; // 已经初始化

            try {
                // 检查ModuleInstance是否存在
                if (typeof window.ModuleInstance === 'undefined') {
                    if (typeof Module !== 'undefined') {
                        // 使用全局Module作为ModuleInstance
                        window.ModuleInstance = Module;
                        log('使用全局Module作为ModuleInstance', 'info');
                    } else {
                        throw new Error('Opus库未加载，ModuleInstance和Module对象都不存在');
                    }
                }

                const mod = window.ModuleInstance;

                // 创建解码器对象
                opusDecoder = {
                    channels: CHANNELS,
                    rate: SAMPLE_RATE,
                    frameSize: FRAME_SIZE,
                    module: mod,
                    decoderPtr: null, // 初始为null

                    // 初始化解码器
                    init: function () {
                        if (this.decoderPtr) return true; // 已经初始化

                        // 获取解码器大小
                        const decoderSize = mod._opus_decoder_get_size(this.channels);
                        log(`Opus解码器大小: ${decoderSize}字节`, 'debug');

                        // 分配内存
                        this.decoderPtr = mod._malloc(decoderSize);
                        if (!this.decoderPtr) {
                            throw new Error("无法分配解码器内存");
                        }

                        // 初始化解码器
                        const err = mod._opus_decoder_init(
                            this.decoderPtr,
                            this.rate,
                            this.channels
                        );

                        if (err < 0) {
                            this.destroy(); // 清理资源
                            throw new Error(`Opus解码器初始化失败: ${err}`);
                        }

                        log("Opus解码器初始化成功", 'success');
                        return true;
                    },

                    // 解码方法
                    decode: function (opusData) {
                        if (!this.decoderPtr) {
                            if (!this.init()) {
                                throw new Error("解码器未初始化且无法初始化");
                            }
                        }

                        try {
                            const mod = this.module;

                            // 为Opus数据分配内存
                            const opusPtr = mod._malloc(opusData.length);
                            mod.HEAPU8.set(opusData, opusPtr);

                            // 为PCM输出分配内存
                            const pcmPtr = mod._malloc(this.frameSize * 2); // Int16 = 2字节

                            // 解码
                            const decodedSamples = mod._opus_decode(
                                this.decoderPtr,
                                opusPtr,
                                opusData.length,
                                pcmPtr,
                                this.frameSize,
                                0 // 不使用FEC
                            );

                            if (decodedSamples < 0) {
                                mod._free(opusPtr);
                                mod._free(pcmPtr);
                                throw new Error(`Opus解码失败: ${decodedSamples}`);
                            }

                            // 复制解码后的数据
                            const decodedData = new Int16Array(decodedSamples);
                            for (let i = 0; i < decodedSamples; i++) {
                                decodedData[i] = mod.HEAP16[(pcmPtr >> 1) + i];
                            }

                            // 释放内存
                            mod._free(opusPtr);
                            mod._free(pcmPtr);

                            return decodedData;
                        } catch (error) {
                            log(`Opus解码错误: ${error.message}`, 'error');
                            return new Int16Array(0);
                        }
                    },

                    // 销毁方法
                    destroy: function () {
                        if (this.decoderPtr) {
                            this.module._free(this.decoderPtr);
                            this.decoderPtr = null;
                        }
                    }
                };

                // 初始化解码器
                if (!opusDecoder.init()) {
                    throw new Error("Opus解码器初始化失败");
                }

                return opusDecoder;

            } catch (error) {
                log(`Opus解码器初始化失败: ${error.message}`, 'error');
                opusDecoder = null; // 重置为null，以便下次重试
                throw error;
            }
        }

        // 初始化音频录制和处理
        async function initAudio() {
            try {
                // 请求麦克风权限
                const stream = await navigator.mediaDevices.getUserMedia({
                    audio: {
                        echoCancellation: true,
                        noiseSuppression: true,
                        sampleRate: 16000,  // 确保16kHz采样率
                        channelCount: 1     // 确保单声道
                    }
                });
                log('已获取麦克风访问权限', 'success');

                // 创建音频上下文
                audioContext = getAudioContextInstance();
                const source = audioContext.createMediaStreamSource(stream);

                // 获取实际音频轨道设置
                const audioTracks = stream.getAudioTracks();
                if (audioTracks.length > 0) {
                    const track = audioTracks[0];
                    const settings = track.getSettings();
                    log(`实际麦克风设置 - 采样率: ${settings.sampleRate || '未知'}Hz, 声道数: ${settings.channelCount || '未知'}`, 'info');
                }

                // 创建分析器用于可视化
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 2048;
                source.connect(analyser);

                // 尝试初始化MediaRecorder，按优先级尝试不同编码选项
                try {
                    // 优先尝试使用Opus编码
                    mediaRecorder = new MediaRecorder(stream, {
                        mimeType: 'audio/webm;codecs=opus',
                        audioBitsPerSecond: 16000
                    });
                    log('已初始化MediaRecorder (使用Opus编码)', 'success');
                    log(`选择的编码格式: ${mediaRecorder.mimeType}`, 'info');
                } catch (e1) {
                    try {
                        // 如果Opus不支持，尝试MP3
                        mediaRecorder = new MediaRecorder(stream, {
                            mimeType: 'audio/webm',
                            audioBitsPerSecond: 16000
                        });
                        log('已初始化MediaRecorder (使用WebM标准编码，Opus不支持)', 'warning');
                        log(`选择的编码格式: ${mediaRecorder.mimeType}`, 'info');
                    } catch (e2) {
                        try {
                            // 尝试其他备选格式
                            mediaRecorder = new MediaRecorder(stream, {
                                mimeType: 'audio/ogg;codecs=opus',
                                audioBitsPerSecond: 16000
                            });
                            log('已初始化MediaRecorder (使用OGG+Opus编码)', 'warning');
                            log(`选择的编码格式: ${mediaRecorder.mimeType}`, 'info');
                        } catch (e3) {
                            // 最后使用默认编码
                            mediaRecorder = new MediaRecorder(stream);
                            log(`已初始化MediaRecorder (使用默认编码: ${mediaRecorder.mimeType})`, 'warning');
                        }
                    }
                }

                // 处理录制的数据
                mediaRecorder.ondataavailable = (event) => {
                    if (event.data.size > 0) {
                        audioChunks.push(event.data);
                    }
                };

                // 录制结束后处理数据
                mediaRecorder.onstop = async () => {
                    // 停止可视化
                    if (visualizationRequest) {
                        cancelAnimationFrame(visualizationRequest);
                        visualizationRequest = null;
                    }

                    log(`录音结束，已收集的音频块数量: ${audioChunks.length}`, 'info');
                    if (audioChunks.length === 0) {
                        log('警告：没有收集到任何音频数据，请检查麦克风是否工作正常', 'error');
                        return;
                    }

                    // 创建完整的录音blob
                    const blob = new Blob(audioChunks, { type: audioChunks[0].type });
                    log(`已创建音频Blob，MIME类型: ${audioChunks[0].type}，大小: ${(blob.size / 1024).toFixed(2)} KB`, 'info');

                    // 保存原始块，以防清空后需要调试
                    const chunks = [...audioChunks];
                    audioChunks = [];

                    try {
                        // 将blob转换为ArrayBuffer
                        const arrayBuffer = await blob.arrayBuffer();
                        const uint8Array = new Uint8Array(arrayBuffer);

                        log(`已转换为Uint8Array，准备发送，大小: ${(arrayBuffer.byteLength / 1024).toFixed(2)} KB`, 'info');

                        // 检查WebSocket状态
                        if (!websocket) {
                            log('错误：WebSocket连接不存在', 'error');
                            return;
                        }

                        if (websocket.readyState !== WebSocket.OPEN) {
                            log(`错误：WebSocket连接未打开，当前状态: ${websocket.readyState}`, 'error');
                            return;
                        }

                        // 直接发送二进制音频数据 - 这是最简单有效的方式
                        try {
                            // 注意：开始和结束消息已在录音开始和结束时发送
                            // 这里只需要发送音频数据
                            await new Promise(resolve => setTimeout(resolve, 50));

                            // 处理WebM容器格式，提取纯Opus数据
                            // 服务器使用opuslib_next.Decoder，需要纯Opus帧
                            log('正在处理音频数据，提取纯Opus帧...', 'info');
                            const opusData = extractOpusFrames(uint8Array);

                            // 记录Opus数据大小
                            log(`已提取Opus数据，大小: ${(opusData.byteLength / 1024).toFixed(2)} KB`, 'info');

                            // 发送音频消息第二步：二进制音频数据
                            websocket.send(opusData);
                            log(`已发送Opus音频数据: ${(opusData.byteLength / 1024).toFixed(2)} KB`, 'success');
                        } catch (error) {
                            log(`音频数据发送失败: ${error.message}`, 'error');

                            // 尝试使用base64编码作为备选方案
                            try {
                                log('尝试使用base64编码方式发送...', 'info');
                                const base64Data = arrayBufferToBase64(arrayBuffer);
                                const audioDataMessage = {
                                    type: 'audio',
                                    action: 'data',
                                    format: 'opus',
                                    sample_rate: 16000,
                                    channels: 1,
                                    mime_type: chunks[0].type,
                                    encoding: 'base64',
                                    data: base64Data
                                };
                                websocket.send(JSON.stringify(audioDataMessage));
                                log(`已使用base64编码发送音频数据: ${(arrayBuffer.byteLength / 1024).toFixed(2)} KB`, 'warning');
                            } catch (base64Error) {
                                log(`所有数据发送方式均失败: ${base64Error.message}`, 'error');
                            }
                        }
                    } catch (error) {
                        log(`处理录音数据错误: ${error.message}`, 'error');
                    }
                };

                // 尝试初始化Opus解码器
                try {
                    // 检查ModuleInstance是否存在（本地库导出的全局变量）
                    if (typeof window.ModuleInstance === 'undefined') {
                        throw new Error('Opus库未加载，ModuleInstance对象不存在');
                    }

                    // 简单测试ModuleInstance是否可用
                    if (typeof window.ModuleInstance._opus_decoder_get_size === 'function') {
                        const testSize = window.ModuleInstance._opus_decoder_get_size(1);
                        log(`Opus解码器测试成功，解码器大小: ${testSize} 字节`, 'success');
                    } else {
                        throw new Error('Opus解码函数未找到');
                    }
                } catch (err) {
                    log(`Opus解码器初始化警告: ${err.message}，将在需要时重试`, 'warning');
                }

                log('音频系统初始化完成', 'success');
                return true;
            } catch (error) {
                log(`音频初始化错误: ${error.message}`, 'error');
                return false;
            }
        }

        // 开始录音
        function startRecording() {
            if (isRecording) return;

            try {
                // 最小录音时长提示
                log('请至少录制1-2秒钟的音频，确保采集到足够数据', 'info');

                // 获取服务器类型 - 从URL判断
                const serverUrl = serverUrlInput.value.trim();
                let isXiaozhiNative = false;

                // 检查是否是小智原生服务器 (根据URL特征判断)
                if (serverUrl.includes('xiaozhi') || serverUrl.includes('localhost') || serverUrl.includes('127.0.0.1')) {
                    isXiaozhiNative = true;
                    log('检测到小智原生服务器，使用标准listen协议', 'info');
                }

                // 使用直接PCM录音和libopus编码的方式
                startDirectRecording();
            } catch (error) {
                log(`录音启动错误: ${error.message}`, 'error');
            }
        }

        // 停止录音
        function stopRecording() {
            if (!isRecording) return;

            try {
                // 使用直接PCM录音停止
                stopDirectRecording();
            } catch (error) {
                log(`停止录音错误: ${error.message}`, 'error');
            }
        }

        // 连接WebSocket服务器
        async function connectToServer() {
            initAudioContext(16000);

            const url = serverUrlInput.value.trim();
            const config = getConfig();
            // 先检查OTA状态
            log('正在检查OTA状态...', 'info');
            const otaUrl = document.getElementById('otaUrl').value.trim();
            localStorage.setItem('otaUrl', otaUrl);
            localStorage.setItem('wsUrl', url);

            try {
                const ws = await webSocketConnect(otaUrl, config)
                if (ws === undefined) {
                    return
                }
                websocket = ws

                // 设置接收二进制数据的类型为ArrayBuffer
                websocket.binaryType = 'arraybuffer';

                websocket.onopen = async () => {
                    log(`已连接到服务器: ${url}`, 'success');
                    connectionStatus.textContent = 'ws已连接';
                    connectionStatus.style.color = 'green';

                    // 连接成功后发送hello消息
                    await sendHelloMessage();

                    connectButton.textContent = '断开';
                    connectButton.removeEventListener('click', connectToServer);
                    connectButton.addEventListener('click', disconnectFromServer);
                    // connectButton.onclick = disconnectFromServer;
                    messageInput.disabled = false;
                    sendTextButton.disabled = false;

                    const audioInitialized = await initAudio();
                    if (audioInitialized) {
                        recordButton.disabled = false;
                    }
                };

                websocket.onclose = () => {
                    log('已断开连接', 'info');
                    connectionStatus.textContent = 'ws已断开';
                    connectionStatus.style.color = 'red';

                    connectButton.textContent = '连接';
                    connectButton.removeEventListener('click', disconnectFromServer);
                    connectButton.addEventListener('click', connectToServer);
                    // connectButton.onclick = connectToServer;
                    messageInput.disabled = true;
                    sendTextButton.disabled = true;
                    recordButton.disabled = true;
                    // stopButton.disabled = true;
                };

                websocket.onerror = (error) => {
                    log(`WebSocket错误: ${error.message || '未知错误'}`, 'error');
                    connectionStatus.textContent = 'ws未连接';
                    connectionStatus.style.color = 'red';
                };

                websocket.onmessage = function (event) {
                    try {
                        // 检查是否为文本消息
                        console.log('收到event消息:', event.data);

                        if (typeof event.data === 'string') {
                            const message = JSON.parse(event.data);
                            console.log('收到string消息:', message);
                            // if(message.state === 'stop'){
                            //     playPCMBufferedAudio(16000,1);
                            // }
                            if (message.type === 'hello') {
                                log(`服务器回应：${JSON.stringify(message, null, 2)}`, 'success');
                            } else if (message.type === 'tts') {
                                // TTS状态消息
                                if (message.state === 'start') {
                                    log('服务器开始发送语音', 'info');
                                } else if (message.state === 'sentence_start') {
                                    log(`服务器发送语音段: ${message.text}`, 'info');
                                    // 添加文本到会话记录
                                    if (message.text) {
                                        addMessage(message.text);
                                    }
                                } else if (message.state === 'sentence_end') {
                                    log(`语音段结束: ${message.text}`, 'info');
                                } else if (message.state === 'stop') {
                                    log('服务器语音传输结束', 'info');
                                    // 结束后更新UI状态
                                    if (recordButton.disabled) {
                                        recordButton.disabled = false;
                                        recordButton.textContent = '开始录音';
                                        recordButton.classList.remove('recording');
                                    }
                                }
                            } else if (message.type === 'audio') {
                                // 音频控制消息
                                log(`收到音频控制消息: ${JSON.stringify(message)}`, 'info');
                            } else if (message.type === 'stt') {
                                // 语音识别结果
                                log(`识别结果: ${message.text}`, 'info');
                                // 添加识别结果到会话记录
                                addMessage(`[语音识别] ${message.text}`, true);
                            } else if (message.type === 'llm') {
                                // 大模型回复
                                log(`大模型回复: ${message.text}`, 'info');
                                // 添加大模型回复到会话记录
                                if (message.text && message.text !== '😊') {
                                    addMessage(message.text);
                                }
                            } else if (message.type === 'mcp') {
                                const payload = message.payload || {};
                                log(`服务器下发: ${JSON.stringify(message)}`, 'info');

                                if (payload.method === 'tools/list') {
                                    // 返回工具列表
                                    const tools = getMcpTools();

                                    const replyMessage = JSON.stringify({
                                        "session_id": message.session_id || "",
                                        "type": "mcp",
                                        "payload": {
                                            "jsonrpc": "2.0",
                                            "id": payload.id,
                                            "result": {
                                                "tools": tools
                                            }
                                        }
                                    });
                                    log(`客户端上报: ${replyMessage}`, 'info');
                                    websocket.send(replyMessage);
                                    log(`回复MCP工具列表: ${tools.length} 个工具`, 'info');

                                } else if (payload.method === 'tools/call') {
                                    // 调用工具
                                    const toolName = payload.params?.name;
                                    const toolArgs = payload.params?.arguments;

                                    log(`调用工具: ${toolName} 参数: ${JSON.stringify(toolArgs)}`, 'info');

                                    // 执行工具
                                    const result = executeMcpTool(toolName, toolArgs);

                                    const replyMessage = JSON.stringify({
                                        "session_id": message.session_id || "",
                                        "type": "mcp",
                                        "payload": {
                                            "jsonrpc": "2.0",
                                            "id": payload.id,
                                            "result": {
                                                "content": [
                                                    {
                                                        "type": "text",
                                                        "text": JSON.stringify(result)
                                                    }
                                                ],
                                                "isError": false
                                            }
                                        }
                                    });

                                    log(`客户端上报: ${replyMessage}`, 'info');

                                    websocket.send(replyMessage);
                                } else if (payload.method === 'initialize') {
                                    log(`收到工具初始化请求: ${JSON.stringify(payload.params)}`, 'info');
                                    // 目前仅记录日志
                                } else {
                                    log(`未知的MCP方法: ${payload.method}`, 'warning');
                                }
                            } else {
                                // 未知消息类型
                                log(`未知消息类型: ${message.type}`, 'info');
                                addMessage(JSON.stringify(message, null, 2));
                            }
                        } else {
                            // 处理二进制数据 - 兼容多种二进制格式
                            // handleBinaryMessage(event.data);
                            feedPCM(event.data, 16000, 1);
                        }
                    } catch (error) {
                        log(`WebSocket消息处理错误: ${error.message}`, 'error');
                        // 非JSON格式文本消息直接显示
                        if (typeof event.data === 'string') {
                            addMessage(event.data);
                        }
                    }
                };

                connectionStatus.textContent = 'ws未连接';
                connectionStatus.style.color = 'orange';
            } catch (error) {
                log(`连接错误: ${error.message}`, 'error');
                connectionStatus.textContent = 'ws未连接';
            }
        }

        // 发送hello握手消息
        async function sendHelloMessage() {
            if (!websocket || websocket.readyState !== WebSocket.OPEN) return;

            try {
                const config = getConfig();

                // 设置设备信息
                const helloMessage = {
                    type: 'hello',
                    device_id: config.deviceId,
                    device_name: config.deviceName,
                    device_mac: config.deviceMac,
                    token: config.token,
                    features: {
                        mcp: true
                    }
                };

                log('发送hello握手消息', 'info');
                websocket.send(JSON.stringify(helloMessage));

                // 等待服务器响应
                return new Promise(resolve => {
                    // 5秒超时
                    const timeout = setTimeout(() => {
                        log('等待hello响应超时', 'error');
                        log('提示: 请尝试点击"测试认证"按钮进行连接排查', 'info');
                        resolve(false);
                    }, 5000);

                    // 临时监听一次消息，接收hello响应
                    const onMessageHandler = (event) => {
                        try {
                            const response = JSON.parse(event.data);
                            if (response.type === 'hello' && response.session_id) {
                                log(`服务器握手成功，会话ID: ${response.session_id}`, 'success');
                                clearTimeout(timeout);
                                websocket.removeEventListener('message', onMessageHandler);
                                resolve(true);
                            }
                        } catch (e) {
                            // 忽略非JSON消息
                        }
                    };

                    websocket.addEventListener('message', onMessageHandler);
                });
            } catch (error) {
                log(`发送hello消息错误: ${error.message}`, 'error');
                return false;
            }
        }

        // 断开WebSocket连接
        function disconnectFromServer() {
            if (!websocket) return;

            websocket.close();
            stopRecording();
        }

        // 发送文本消息
        function sendTextMessage() {
            const message = messageInput.value.trim();
            if (message === '' || !websocket || websocket.readyState !== WebSocket.OPEN) return;


            try {
                // 直接发送listen消息，不需要重复发送hello
                const listenMessage = {
                    type: 'listen',
                    mode: 'manual',
                    state: 'detect',
                    text: message
                };

                websocket.send(JSON.stringify(listenMessage));
                addMessage(message, true);
                log(`发送文本消息: ${message}`, 'info');

                messageInput.value = '';
            } catch (error) {
                log(`发送消息错误: ${error.message}`, 'error');
            }
        }

        // 生成随机MAC地址
        function generateRandomMac() {
            const hexDigits = '0123456789ABCDEF';
            let mac = '';
            for (let i = 0; i < 6; i++) {
                if (i > 0) mac += ':';
                for (let j = 0; j < 2; j++) {
                    mac += hexDigits.charAt(Math.floor(Math.random() * 16));
                }
            }
            return mac;
        }

        // 初始化事件监听器
        function initEventListeners() {
            connectButton.addEventListener('click', connectToServer);

            // 设备配置面板折叠/展开
            const toggleButton = document.getElementById('toggleConfig');
            const configPanel = document.getElementById('configPanel');
            const deviceMacInput = document.getElementById('deviceMac');
            const clientIdInput = document.getElementById('clientId');
            const displayMac = document.getElementById('displayMac');
            const displayClient = document.getElementById('displayClient');

            // 从localStorage加载MAC地址，如果没有则生成新的
            let savedMac = localStorage.getItem('deviceMac');
            if (!savedMac) {
                savedMac = generateRandomMac();
                localStorage.setItem('deviceMac', savedMac);
            }
            deviceMacInput.value = savedMac;
            displayMac.textContent = savedMac;

            // 更新显示的值
            function updateDisplayValues() {
                const newMac = deviceMacInput.value;
                displayMac.textContent = newMac;
                displayClient.textContent = clientIdInput.value;
                // 保存MAC地址到localStorage
                localStorage.setItem('deviceMac', newMac);
            }

            // 监听输入变化
            deviceMacInput.addEventListener('input', updateDisplayValues);
            clientIdInput.addEventListener('input', updateDisplayValues);

            // 初始更新显示值
            updateDisplayValues();

            const savedOtaUrl = localStorage.getItem('otaUrl');
            if (savedOtaUrl) {
                document.getElementById('otaUrl').value = savedOtaUrl;
            }

            // 切换面板显示
            toggleButton.addEventListener('click', () => {
                const isExpanded = configPanel.classList.contains('expanded');
                configPanel.classList.toggle('expanded');
                toggleButton.textContent = isExpanded ? '编辑' : '收起';
            });

            // 标签页切换
            const tabs = document.querySelectorAll('.tab');
            tabs.forEach(tab => {
                tab.addEventListener('click', () => {
                    // 移除所有标签页的active类
                    tabs.forEach(t => t.classList.remove('active'));
                    document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));

                    // 添加当前标签页的active类
                    tab.classList.add('active');
                    document.getElementById(`${tab.dataset.tab}Tab`).classList.add('active');
                });
            });

            sendTextButton.addEventListener('click', sendTextMessage);
            messageInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') sendTextMessage();
            });

            recordButton.addEventListener('click', () => {
                if (isRecording) {
                    stopRecording();
                } else {
                    startRecording();
                }
            });

            window.addEventListener('resize', initVisualizer);
        }

        // 帮助函数：ArrayBuffer转Base64
        function arrayBufferToBase64(buffer) {
            let binary = '';
            const bytes = new Uint8Array(buffer);
            const len = bytes.byteLength;
            for (let i = 0; i < len; i++) {
                binary += String.fromCharCode(bytes[i]);
            }
            return window.btoa(binary);
        }

        // Opus编码器
        let opusEncoder;

        // 初始化应用
        function initApp() {
            initVisualizer();
            initEventListeners();

            // 检查libopus.js是否正确加载
            checkOpusLoaded();

            // 初始化Opus编码器
            opusEncoder = initOpusEncoder();

            // 预加载Opus解码器
            log('预加载Opus解码器...', 'info');
            initOpusDecoder().then(() => {
                log('Opus解码器预加载成功', 'success');
            }).catch(error => {
                log(`Opus解码器预加载失败: ${error.message}，将在需要时重试`, 'warning');
            });
            playBufferedAudio()
            startAudioBuffering()

        }

        // PCM录音处理器代码 - 会被注入到AudioWorklet中
        const audioProcessorCode = `
            class AudioRecorderProcessor extends AudioWorkletProcessor {
                constructor() {
                    super();
                    this.buffers = [];
                    this.frameSize = 960; // 60ms @ 16kHz = 960 samples
                    this.buffer = new Int16Array(this.frameSize);
                    this.bufferIndex = 0;
                    this.isRecording = false;

                    // 监听来自主线程的消息
                    this.port.onmessage = (event) => {
                        if (event.data.command === 'start') {
                            this.isRecording = true;
                            this.port.postMessage({ type: 'status', status: 'started' });
                        } else if (event.data.command === 'stop') {
                            this.isRecording = false;

                            // 发送剩余的缓冲区
                            if (this.bufferIndex > 0) {
                                const finalBuffer = this.buffer.slice(0, this.bufferIndex);
                                this.port.postMessage({
                                    type: 'buffer',
                                    buffer: finalBuffer
                                });
                                this.bufferIndex = 0;
                            }

                            this.port.postMessage({ type: 'status', status: 'stopped' });
                        }
                    };
                }

                process(inputs, outputs, parameters) {
                    if (!this.isRecording) return true;

                    const input = inputs[0][0]; // 获取第一个输入通道
                    if (!input) return true;

                    // 将浮点采样转换为16位整数并存储
                    for (let i = 0; i < input.length; i++) {
                        if (this.bufferIndex >= this.frameSize) {
                            // 缓冲区已满，发送给主线程并重置
                            this.port.postMessage({
                                type: 'buffer',
                                buffer: this.buffer.slice(0)
                            });
                            this.bufferIndex = 0;
                        }

                        // 转换为16位整数 (-32768到32767)
                        this.buffer[this.bufferIndex++] = Math.max(-32768, Math.min(32767, Math.floor(input[i] * 32767)));
                    }

                    return true;
                }
            }

            registerProcessor('audio-recorder-processor', AudioRecorderProcessor);
        `;

        // 创建音频处理器
        async function createAudioProcessor() {
            audioContext = getAudioContextInstance();

            try {
                // 检查是否支持AudioWorklet
                if (audioContext.audioWorklet) {
                    // 注册音频处理器
                    const blob = new Blob([audioProcessorCode], { type: 'application/javascript' });
                    const url = URL.createObjectURL(blob);
                    await audioContext.audioWorklet.addModule(url);
                    URL.revokeObjectURL(url);

                    // 创建音频处理节点
                    const audioProcessor = new AudioWorkletNode(audioContext, 'audio-recorder-processor');

                    // 设置音频处理消息处理
                    audioProcessor.port.onmessage = (event) => {
                        if (event.data.type === 'buffer') {
                            // 收到PCM缓冲区数据
                            processPCMBuffer(event.data.buffer);
                        }
                    };

                    log('使用AudioWorklet处理音频', 'success');
                    return { node: audioProcessor, type: 'worklet' };
                } else {
                    // 使用旧版ScriptProcessorNode作为回退方案
                    log('AudioWorklet不可用，使用ScriptProcessorNode作为回退方案', 'warning');

                    const frameSize = 4096; // ScriptProcessorNode缓冲区大小
                    const scriptProcessor = audioContext.createScriptProcessor(frameSize, 1, 1);

                    // 将audioProcess事件设置为处理音频数据
                    scriptProcessor.onaudioprocess = (event) => {
                        if (!isRecording) return;

                        const input = event.inputBuffer.getChannelData(0);
                        const buffer = new Int16Array(input.length);

                        // 将浮点数据转换为16位整数
                        for (let i = 0; i < input.length; i++) {
                            buffer[i] = Math.max(-32768, Math.min(32767, Math.floor(input[i] * 32767)));
                        }

                        // 处理PCM数据
                        processPCMBuffer(buffer);
                    };

                    // 需要连接输出，否则不会触发处理
                    // 我们创建一个静音通道
                    const silent = audioContext.createGain();
                    silent.gain.value = 0;
                    scriptProcessor.connect(silent);
                    silent.connect(audioContext.destination);

                    return { node: scriptProcessor, type: 'processor' };
                }
            } catch (error) {
                log(`创建音频处理器失败: ${error.message}，尝试回退方案`, 'error');

                // 最后回退方案：使用ScriptProcessorNode
                try {
                    const frameSize = 4096; // ScriptProcessorNode缓冲区大小
                    const scriptProcessor = audioContext.createScriptProcessor(frameSize, 1, 1);

                    scriptProcessor.onaudioprocess = (event) => {
                        if (!isRecording) return;

                        const input = event.inputBuffer.getChannelData(0);
                        const buffer = new Int16Array(input.length);

                        for (let i = 0; i < input.length; i++) {
                            buffer[i] = Math.max(-32768, Math.min(32767, Math.floor(input[i] * 32767)));
                        }

                        processPCMBuffer(buffer);
                    };

                    const silent = audioContext.createGain();
                    silent.gain.value = 0;
                    scriptProcessor.connect(silent);
                    silent.connect(audioContext.destination);

                    log('使用ScriptProcessorNode作为回退方案成功', 'warning');
                    return { node: scriptProcessor, type: 'processor' };
                } catch (fallbackError) {
                    log(`回退方案也失败: ${fallbackError.message}`, 'error');
                    return null;
                }
            }
        }

        // 初始化直接从PCM数据录音的系统
        let audioProcessor = null;
        let audioProcessorType = null;
        let audioSource = null;

        // 处理PCM缓冲数据
        let pcmDataBuffer = new Int16Array();

        function processPCMBuffer(buffer) {
            if (!isRecording) return;

            // 将新的PCM数据追加到缓冲区
            const newBuffer = new Int16Array(pcmDataBuffer.length + buffer.length);
            newBuffer.set(pcmDataBuffer);
            newBuffer.set(buffer, pcmDataBuffer.length);
            pcmDataBuffer = newBuffer;

            // 检查是否有足够的数据进行Opus编码（16000Hz, 60ms = 960个采样点）
            const samplesPerFrame = 960; // 60ms @ 16kHz

            while (pcmDataBuffer.length >= samplesPerFrame) {
                // 从缓冲区取出一帧数据
                const frameData = pcmDataBuffer.slice(0, samplesPerFrame);
                pcmDataBuffer = pcmDataBuffer.slice(samplesPerFrame);

                // 编码为Opus
                encodeAndSendOpus(frameData);
            }
        }

        // 编码并发送Opus数据
        function encodeAndSendOpus(pcmData = null) {
            if (!opusEncoder) {
                log('Opus编码器未初始化', 'error');
                return;
            }

            try {
                // 如果提供了PCM数据，则编码该数据
                if (pcmData) {
                    // 使用已初始化的Opus编码器编码
                    const opusData = opusEncoder.encode(pcmData);

                    if (opusData && opusData.length > 0) {
                        // 存储音频帧
                        audioBuffers.push(opusData.buffer);
                        totalAudioSize += opusData.length;

                        // 如果WebSocket已连接，则发送数据
                        if (websocket && websocket.readyState === WebSocket.OPEN) {
                            try {
                                // 服务端期望接收原始Opus数据，不需要任何额外包装
                                websocket.send(opusData.buffer);
                                log(`发送Opus帧，大小：${opusData.length}字节`, 'debug');
                            } catch (error) {
                                log(`WebSocket发送错误: ${error.message}`, 'error');
                            }
                        }
                    } else {
                        log('Opus编码失败，无有效数据返回', 'error');
                    }
                } else {
                    // 处理剩余的PCM数据
                    if (pcmDataBuffer.length > 0) {
                        // 如果剩余的采样点不足一帧，用静音填充
                        const samplesPerFrame = 960;
                        if (pcmDataBuffer.length < samplesPerFrame) {
                            const paddedBuffer = new Int16Array(samplesPerFrame);
                            paddedBuffer.set(pcmDataBuffer);
                            // 剩余部分为0（静音）
                            encodeAndSendOpus(paddedBuffer);
                        } else {
                            encodeAndSendOpus(pcmDataBuffer.slice(0, samplesPerFrame));
                        }
                        pcmDataBuffer = new Int16Array(0);
                    }
                }
            } catch (error) {
                log(`Opus编码错误: ${error.message}`, 'error');
            }
        }

        // 开始直接从PCM数据录音
        async function startDirectRecording() {
            if (isRecording) return;

            try {
                // 初始化Opus编码器
                if (!initOpusEncoder()) {
                    log('无法启动录音: Opus编码器初始化失败', 'error');
                    return;
                }

                // 请求麦克风权限
                const stream = await navigator.mediaDevices.getUserMedia({
                    audio: {
                        echoCancellation: true,
                        noiseSuppression: true,
                        sampleRate: 16000,
                        channelCount: 1
                    }
                });

                // 创建音频上下文和分析器
                audioContext = getAudioContextInstance();

                // 创建音频处理器
                const processorResult = await createAudioProcessor();
                if (!processorResult) {
                    log('无法创建音频处理器', 'error');
                    return;
                }

                audioProcessor = processorResult.node;
                audioProcessorType = processorResult.type;

                // 连接音频处理链
                audioSource = audioContext.createMediaStreamSource(stream);
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 2048;

                audioSource.connect(analyser);
                audioSource.connect(audioProcessor);

                // 启动录音
                pcmDataBuffer = new Int16Array();
                audioBuffers = [];
                totalAudioSize = 0;
                isRecording = true;

                // 启动音频处理器的录音 - 只有AudioWorklet才需要发送消息
                if (audioProcessorType === 'worklet' && audioProcessor.port) {
                    audioProcessor.port.postMessage({ command: 'start' });
                }

                // 发送监听开始消息
                if (websocket && websocket.readyState === WebSocket.OPEN) {
                    // 使用与服务端期望的listen消息格式
                    const listenMessage = {
                        type: 'listen',
                        mode: 'manual',  // 使用手动模式，由我们控制开始/停止
                        state: 'start'   // 表示开始录音
                    };

                    log(`发送录音开始消息: ${JSON.stringify(listenMessage)}`, 'info');
                    websocket.send(JSON.stringify(listenMessage));
                } else {
                    log('WebSocket未连接，无法发送开始消息', 'error');
                    return false;
                }

                // 开始音频可视化
                const dataArray = new Uint8Array(analyser.frequencyBinCount);
                drawVisualizer(dataArray);

                // 在UI上显示录音计时器
                let recordingSeconds = 0;
                const recordingTimer = setInterval(() => {
                    recordingSeconds += 0.1;
                    recordButton.textContent = `停止录音 ${recordingSeconds.toFixed(1)}秒`;
                }, 100);

                // 保存计时器，以便在停止时清除
                window.recordingTimer = recordingTimer;

                recordButton.classList.add('recording');
                recordButton.disabled = false;

                log('开始PCM直接录音', 'success');
                return true;
            } catch (error) {
                log(`直接录音启动错误: ${error.message}`, 'error');
                isRecording = false;
                return false;
            }
        }

        // 停止直接从PCM数据录音
        function stopDirectRecording() {
            if (!isRecording) return;

            try {
                // 停止录音
                isRecording = false;

                // 停止音频处理器的录音
                if (audioProcessor) {
                    // 只有AudioWorklet才需要发送停止消息
                    if (audioProcessorType === 'worklet' && audioProcessor.port) {
                        audioProcessor.port.postMessage({ command: 'stop' });
                    }

                    audioProcessor.disconnect();
                    audioProcessor = null;
                }

                // 断开音频连接
                if (audioSource) {
                    audioSource.disconnect();
                    audioSource = null;
                }

                // 停止可视化
                if (visualizationRequest) {
                    cancelAnimationFrame(visualizationRequest);
                    visualizationRequest = null;
                }

                // 清除录音计时器
                if (window.recordingTimer) {
                    clearInterval(window.recordingTimer);
                    window.recordingTimer = null;
                }

                // 编码并发送剩余的数据
                encodeAndSendOpus();

                // 发送一个空的消息作为结束标志（模拟接收到空音频数据的情况）
                if (websocket && websocket.readyState === WebSocket.OPEN) {
                    // 使用空的Uint8Array发送最后一个空帧
                    const emptyOpusFrame = new Uint8Array(0);
                    websocket.send(emptyOpusFrame);

                    // 发送监听结束消息
                    const stopMessage = {
                        type: 'listen',
                        mode: 'manual',
                        state: 'stop'
                    };

                    websocket.send(JSON.stringify(stopMessage));
                    log('已发送录音停止信号', 'info');
                }

                // 重置UI
                recordButton.textContent = '开始录音';
                recordButton.classList.remove('recording');
                recordButton.disabled = false;

                log('停止PCM直接录音', 'success');
                return true;
            } catch (error) {
                log(`直接录音停止错误: ${error.message}`, 'error');
                return false;
            }
        }
        // 音频上下文（全局只创建一次）
        let audioCtx = null;
        let bufferQueue = [];
        let isPlaying2 = false;

        // 流式播放调度相关变量
        let streamingNextTime = 0;       // audioCtx 时间线上下一段的起始时间
        let currentSampleRate = SAMPLE_RATE;
        const SCHEDULE_AHEAD = 0.8;      // 向前调度多少秒
        const INITIAL_LATENCY = 0.08;    // 首次播放预留延迟（秒）
        const FADE_MS = 2;               // 每段边界的淡入/淡出时长（毫秒）

        // 初始化音频环境
        function initAudioContext(sampleRate = 16000) {
            if (!audioCtx) {
                audioCtx = new (window.AudioContext || window.webkitAudioContext)({
                    sampleRate: sampleRate,
                });
            }
        }

        // 处理PCM数据并入队播放（流式）：服务器不带时间戳，直接解析整个 ArrayBuffer
        function feedPCM(arrayBuffer, sampleRate = 16000, numChannels = 1) {
            // 确保 audioCtx 已初始化
            if (!audioCtx) initAudioContext(sampleRate);

            currentSampleRate = sampleRate || currentSampleRate;

            // 直接使用完整的 arrayBuffer
            const pcmBuffer = arrayBuffer;
            const view = new DataView(pcmBuffer);
            const samples = new Float32Array(pcmBuffer.byteLength / 2);
            for (let i = 0; i < samples.length; i++) {
                samples[i] = view.getInt16(i * 2, true) / 0x8000;
            }

            bufferQueue.push(samples);

            // 调度队列中的片段到 audioCtx 时间线上
            scheduleChunks();
        }

        // 将队列中的片段调度到 audioCtx 时间线上（使用短淡入淡出避免点击/噪音）
        function scheduleChunks() {
            if (!audioCtx) return;

            if (!streamingNextTime || streamingNextTime < audioCtx.currentTime) {
                streamingNextTime = audioCtx.currentTime + INITIAL_LATENCY;
            }

            const maxAhead = audioCtx.currentTime + SCHEDULE_AHEAD;

            while (bufferQueue.length > 0 && streamingNextTime < maxAhead) {
                const samples = bufferQueue.shift();
                if (!samples || samples.length === 0) continue;

                const numChannels = 1;
                const audioBuffer = audioCtx.createBuffer(numChannels, samples.length, currentSampleRate);
                audioBuffer.getChannelData(0).set(samples);

                const source = audioCtx.createBufferSource();
                source.buffer = audioBuffer;

                const gain = audioCtx.createGain();
                source.connect(gain);
                gain.connect(audioCtx.destination);

                const fadeSec = Math.max(0.001, FADE_MS / 1000);
                const segStart = streamingNextTime;
                const segEnd = segStart + audioBuffer.duration;

                gain.gain.setValueAtTime(0.0, segStart);
                gain.gain.linearRampToValueAtTime(1.0, segStart + fadeSec);
                if (audioBuffer.duration > fadeSec * 2) {
                    gain.gain.setValueAtTime(1.0, segEnd - fadeSec);
                    gain.gain.linearRampToValueAtTime(0.0, segEnd);
                } else {
                    gain.gain.linearRampToValueAtTime(0.0, segEnd);
                }

                try {
                    source.start(segStart);
                    source.stop(segEnd + 0.02);
                } catch (e) {
                    source.start();
                }

                streamingNextTime = segEnd;
                isPlaying2 = true;
            }

            if (bufferQueue.length === 0 && streamingNextTime <= audioCtx.currentTime + 0.001) {
                isPlaying2 = false;
                streamingNextTime = 0;
            }
        }

        // 停止播放并清空队列（保留 audioCtx 以便快速恢复）
        function stopPlay() {
            bufferQueue = [];
            isPlaying2 = false;
            streamingNextTime = 0;
        }

        // 一次性播放全部缓冲的 PCM 数据
        function playPCMBufferedAudio(sampleRate = 16000, numChannels = 1) {
            if (!audioCtx) {
                audioCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate });
            }
            if (bufferQueue.length === 0) {
                console.log('没有可播放的音频数据', 'warning');
                return;
            }
            // 拼接所有 Float32Array
            let totalLength = bufferQueue.reduce((sum, arr) => sum + arr.length, 0);
            let allSamples = new Float32Array(totalLength);
            let offset = 0;
            for (let arr of bufferQueue) {
                allSamples.set(arr, offset);
                offset += arr.length;
            }
            // 创建 AudioBuffer
            const audioBuffer = audioCtx.createBuffer(numChannels, allSamples.length, sampleRate);
            audioBuffer.getChannelData(0).set(allSamples);
            // 播放
            const source = audioCtx.createBufferSource();
            source.buffer = audioBuffer;
            source.connect(audioCtx.destination);
            source.start();
            console.log(`已开始播放全部音频，总长度 ${(audioBuffer.duration).toFixed(2)} 秒`, 'success');
            // 清空队列，防止重复播放
            bufferQueue = [];
            isPlaying2 = false;
        }

        async function handleBinaryMessage(data) {
            try {
                let arrayBuffer;
                // 根据数据类型进行处理
                if (data instanceof ArrayBuffer) {
                    arrayBuffer = data;
                    log(`收到ArrayBuffer音频数据，大小: ${data.byteLength}字节`, 'debug');
                } else if (data instanceof Blob) {
                    // 如果是Blob类型，转换为ArrayBuffer
                    arrayBuffer = await data.arrayBuffer();
                    log(`收到Blob音频数据，大小: ${arrayBuffer.byteLength}字节`, 'debug');
                } else {
                    log(`收到未知类型的二进制数据: ${typeof data}`, 'warning');
                    return;
                }
                if (arrayBuffer.byteLength > 0) {
                    // 正常PCM数据帧
                    feedPCM(arrayBuffer, 16000, 1);
                } else {
                    // 收到空帧或结束标志，开始播放全部数据
                    console.log('收到PCM音频结束标志，准备播放全部数据', 'info');
                    playNext(sampleRate, numChannels);
                }
                // 创建Uint8Array用于处理
                // const opusData = new Uint8Array(arrayBuffer);
                // if (opusData.length > 0) {
                //     // 将数据添加到缓冲队列
                //     queue.enqueue(opusData);
                // } else {
                //     log('收到空音频数据帧，可能是结束标志', 'warning');
                //     // 如果正在播放，发送结束信号
                //     if (isAudioPlaying && streamingContext) {
                //         streamingContext.endOfStream = true;
                //     }
                // }
            } catch (error) {
                log(`处理二进制消息出错: ${error.message}`, 'error');
            }
        }

        // 获取配置值
        function getConfig() {
            const deviceMac = document.getElementById('deviceMac').value.trim();
            return {
                deviceId: deviceMac,  // 使用MAC地址作为deviceId
                deviceName: document.getElementById('deviceName').value.trim(),
                deviceMac: deviceMac,
                clientId: document.getElementById('clientId').value.trim(),
                token: document.getElementById('token').value.trim()
            };
        }

        // ==========================================
        // MCP 工具管理逻辑
        // ==========================================

        // 默认工具数据
        const defaultMcpTools = await fetch("default-mcp-tools.json").then(res => res.json());

        // 全局变量
        let mcpTools = [];
        let mcpEditingIndex = null;
        let mcpProperties = [];

        // 初始化 MCP 工具
        function initMcpTools() {
            const savedTools = localStorage.getItem('mcpTools');
            if (savedTools) {
                try {
                    mcpTools = JSON.parse(savedTools);
                } catch (e) {
                    log('加载MCP工具失败，使用默认工具', 'warning');
                    mcpTools = [...defaultMcpTools];
                }
            } else {
                mcpTools = [...defaultMcpTools];
            }

            renderMcpTools();
            setupMcpEventListeners();
        }

        // 渲染工具列表
        function renderMcpTools() {
            const container = document.getElementById('mcpToolsContainer');
            const countSpan = document.getElementById('mcpToolsCount');

            countSpan.textContent = `${mcpTools.length} 个工具`;

            if (mcpTools.length === 0) {
                container.innerHTML = '<div style="text-align: center; padding: 30px; color: #999;">暂无工具，点击下方按钮添加新工具</div>';
                return;
            }

            container.innerHTML = mcpTools.map((tool, index) => {
                const paramCount = tool.inputSchema.properties ? Object.keys(tool.inputSchema.properties).length : 0;
                const requiredCount = tool.inputSchema.required ? tool.inputSchema.required.length : 0;
                const hasMockResponse = tool.mockResponse && Object.keys(tool.mockResponse).length > 0;

                return `
                    <div class="mcp-tool-card">
                        <div class="mcp-tool-header">
                            <div class="mcp-tool-name">${tool.name}</div>
                            <div class="mcp-tool-actions">
                                <button onclick="editMcpTool(${index})"
                                    style="padding: 4px 10px; border: none; border-radius: 4px; background-color: #2196f3; color: white; cursor: pointer; font-size: 12px;">
                                    ✏️ 编辑
                                </button>
                                <button onclick="deleteMcpTool(${index})"
                                    style="padding: 4px 10px; border: none; border-radius: 4px; background-color: #f44336; color: white; cursor: pointer; font-size: 12px;">
                                    🗑️ 删除
                                </button>
                            </div>
                        </div>
                        <div class="mcp-tool-description">${tool.description}</div>
                        <div class="mcp-tool-info">
                            <div class="mcp-tool-info-row">
                                <span class="mcp-tool-info-label">参数数量:</span>
                                <span class="mcp-tool-info-value">${paramCount} 个 ${requiredCount > 0 ? `(${requiredCount} 个必填)` : ''}</span>
                            </div>
                            <div class="mcp-tool-info-row">
                                <span class="mcp-tool-info-label">模拟返回:</span>
                                <span class="mcp-tool-info-value">${hasMockResponse ? '✅ 已配置: ' + JSON.stringify(tool.mockResponse) : '⚪ 使用默认'}</span>
                            </div>
                        </div>
                    </div>
                `;
            }).join('');
        }

        // 渲染参数列表
        function renderMcpProperties() {
            const container = document.getElementById('mcpPropertiesContainer');

            if (mcpProperties.length === 0) {
                container.innerHTML = '<div style="text-align: center; padding: 20px; color: #999; font-size: 14px;">暂无参数，点击下方按钮添加参数</div>';
                return;
            }

            container.innerHTML = mcpProperties.map((prop, index) => `
                <div class="mcp-property-item">
                    <div class="mcp-property-header">
                        <span class="mcp-property-name">${prop.name}</span>
                        <button type="button" onclick="deleteMcpProperty(${index})"
                            style="padding: 3px 8px; border: none; border-radius: 3px; background-color: #f44336; color: white; cursor: pointer; font-size: 11px;">
                            删除
                        </button>
                    </div>
                    <div class="mcp-property-row">
                        <div>
                            <label class="mcp-small-label">参数名称 *</label>
                            <input type="text" class="mcp-small-input" value="${prop.name}" 
                                onchange="updateMcpProperty(${index}, 'name', this.value)" required>
                        </div>
                        <div>
                            <label class="mcp-small-label">数据类型 *</label>
                            <select class="mcp-small-input" onchange="updateMcpProperty(${index}, 'type', this.value)">
                                <option value="string" ${prop.type === 'string' ? 'selected' : ''}>字符串</option>
                                <option value="integer" ${prop.type === 'integer' ? 'selected' : ''}>整数</option>
                                <option value="number" ${prop.type === 'number' ? 'selected' : ''}>数字</option>
                                <option value="boolean" ${prop.type === 'boolean' ? 'selected' : ''}>布尔值</option>
                                <option value="array" ${prop.type === 'array' ? 'selected' : ''}>数组</option>
                                <option value="object" ${prop.type === 'object' ? 'selected' : ''}>对象</option>
                            </select>
                        </div>
                    </div>
                    ${(prop.type === 'integer' || prop.type === 'number') ? `
                    <div class="mcp-property-row">
                        <div>
                            <label class="mcp-small-label">最小值</label>
                            <input type="number" class="mcp-small-input" value="${prop.minimum !== undefined ? prop.minimum : ''}" 
                                placeholder="可选" onchange="updateMcpProperty(${index}, 'minimum', this.value ? parseFloat(this.value) : undefined)">
                        </div>
                        <div>
                            <label class="mcp-small-label">最大值</label>
                            <input type="number" class="mcp-small-input" value="${prop.maximum !== undefined ? prop.maximum : ''}" 
                                placeholder="可选" onchange="updateMcpProperty(${index}, 'maximum', this.value ? parseFloat(this.value) : undefined)">
                        </div>
                    </div>
                    ` : ''}
                    <div class="mcp-property-row-full">
                        <label class="mcp-small-label">参数描述</label>
                        <input type="text" class="mcp-small-input" value="${prop.description || ''}" 
                            placeholder="可选" onchange="updateMcpProperty(${index}, 'description', this.value)">
                    </div>
                    <label class="mcp-checkbox-label">
                        <input type="checkbox" ${prop.required ? 'checked' : ''} 
                            onchange="updateMcpProperty(${index}, 'required', this.checked)">
                        必填参数
                    </label>
                </div>
            `).join('');
        }

        // 添加参数
        function addMcpProperty() {
            mcpProperties.push({
                name: `param_${mcpProperties.length + 1}`,
                type: 'string',
                required: false,
                description: ''
            });
            renderMcpProperties();
        }

        // 更新参数
        window.updateMcpProperty = function (index, field, value) {
            if (field === 'name') {
                const isDuplicate = mcpProperties.some((p, i) => i !== index && p.name === value);
                if (isDuplicate) {
                    alert('参数名称已存在，请使用不同的名称');
                    renderMcpProperties();
                    return;
                }
            }

            mcpProperties[index][field] = value;

            if (field === 'type' && value !== 'integer' && value !== 'number') {
                delete mcpProperties[index].minimum;
                delete mcpProperties[index].maximum;
                renderMcpProperties();
            }
        };

        // 删除参数
        window.deleteMcpProperty = function (index) {
            mcpProperties.splice(index, 1);
            renderMcpProperties();
        };

        // 设置事件监听
        function setupMcpEventListeners() {
            const toggleBtn = document.getElementById('toggleMcpTools');
            const panel = document.getElementById('mcpToolsPanel');
            const addBtn = document.getElementById('addMcpToolBtn');
            const modal = document.getElementById('mcpToolModal');
            const closeBtn = document.getElementById('closeMcpModalBtn');
            const cancelBtn = document.getElementById('cancelMcpBtn');
            const form = document.getElementById('mcpToolForm');
            const addPropertyBtn = document.getElementById('addMcpPropertyBtn');

            toggleBtn.addEventListener('click', () => {
                const isExpanded = panel.classList.contains('expanded');
                panel.classList.toggle('expanded');
                toggleBtn.textContent = isExpanded ? '展开' : '收起';
            });

            addBtn.addEventListener('click', () => openMcpModal());
            closeBtn.addEventListener('click', closeMcpModal);
            cancelBtn.addEventListener('click', closeMcpModal);
            addPropertyBtn.addEventListener('click', addMcpProperty);

            modal.addEventListener('click', (e) => {
                if (e.target === modal) closeMcpModal();
            });

            form.addEventListener('submit', handleMcpSubmit);
        }

        // 打开模态框
        function openMcpModal(index = null) {
            const isConnected = websocket && websocket.readyState === WebSocket.OPEN;
            if (isConnected) {
                alert('WebSocket 已连接，无法编辑工具');
                return;
            }

            mcpEditingIndex = index;
            const errorContainer = document.getElementById('mcpErrorContainer');
            errorContainer.innerHTML = '';

            if (index !== null) {
                document.getElementById('mcpModalTitle').textContent = '编辑工具';
                const tool = mcpTools[index];
                document.getElementById('mcpToolName').value = tool.name;
                document.getElementById('mcpToolDescription').value = tool.description;
                document.getElementById('mcpMockResponse').value = tool.mockResponse ? JSON.stringify(tool.mockResponse, null, 2) : '';

                mcpProperties = [];
                const schema = tool.inputSchema;
                if (schema.properties) {
                    Object.keys(schema.properties).forEach(key => {
                        const prop = schema.properties[key];
                        mcpProperties.push({
                            name: key,
                            type: prop.type || 'string',
                            minimum: prop.minimum,
                            maximum: prop.maximum,
                            description: prop.description || '',
                            required: schema.required && schema.required.includes(key)
                        });
                    });
                }
            } else {
                document.getElementById('mcpModalTitle').textContent = '添加工具';
                document.getElementById('mcpToolForm').reset();
                mcpProperties = [];
            }

            renderMcpProperties();
            document.getElementById('mcpToolModal').style.display = 'block';
        }

        // 关闭模态框
        function closeMcpModal() {
            document.getElementById('mcpToolModal').style.display = 'none';
            mcpEditingIndex = null;
            document.getElementById('mcpToolForm').reset();
            mcpProperties = [];
            document.getElementById('mcpErrorContainer').innerHTML = '';
        }

        // 处理表单提交
        function handleMcpSubmit(e) {
            e.preventDefault();
            const errorContainer = document.getElementById('mcpErrorContainer');
            errorContainer.innerHTML = '';

            const name = document.getElementById('mcpToolName').value.trim();
            const description = document.getElementById('mcpToolDescription').value.trim();
            const mockResponseText = document.getElementById('mcpMockResponse').value.trim();

            // 检查名称重复
            const isDuplicate = mcpTools.some((tool, index) =>
                tool.name === name && index !== mcpEditingIndex
            );

            if (isDuplicate) {
                showMcpError('工具名称已存在，请使用不同的名称');
                return;
            }

            // 解析模拟返回结果
            let mockResponse = null;
            if (mockResponseText) {
                try {
                    mockResponse = JSON.parse(mockResponseText);
                } catch (e) {
                    showMcpError('模拟返回结果不是有效的 JSON 格式: ' + e.message);
                    return;
                }
            }

            // 构建 inputSchema
            const inputSchema = {
                type: "object",
                properties: {},
                required: []
            };

            mcpProperties.forEach(prop => {
                const propSchema = { type: prop.type };

                if (prop.description) {
                    propSchema.description = prop.description;
                }

                if ((prop.type === 'integer' || prop.type === 'number')) {
                    if (prop.minimum !== undefined && prop.minimum !== '') {
                        propSchema.minimum = prop.minimum;
                    }
                    if (prop.maximum !== undefined && prop.maximum !== '') {
                        propSchema.maximum = prop.maximum;
                    }
                }

                inputSchema.properties[prop.name] = propSchema;

                if (prop.required) {
                    inputSchema.required.push(prop.name);
                }
            });

            if (inputSchema.required.length === 0) {
                delete inputSchema.required;
            }

            const tool = { name, description, inputSchema, mockResponse };

            if (mcpEditingIndex !== null) {
                mcpTools[mcpEditingIndex] = tool;
                log(`已更新工具: ${name}`, 'success');
            } else {
                mcpTools.push(tool);
                log(`已添加工具: ${name}`, 'success');
            }

            saveMcpTools();
            renderMcpTools();
            closeMcpModal();
        }

        // 显示错误
        function showMcpError(message) {
            const errorContainer = document.getElementById('mcpErrorContainer');
            errorContainer.innerHTML = `<div class="mcp-error">${message}</div>`;
        }

        // 编辑工具
        window.editMcpTool = function (index) {
            openMcpModal(index);
        };

        // 删除工具
        window.deleteMcpTool = function (index) {
            const isConnected = websocket && websocket.readyState === WebSocket.OPEN;
            if (isConnected) {
                alert('WebSocket 已连接，无法编辑工具');
                return;
            }
            if (confirm(`确定要删除工具 "${mcpTools[index].name}" 吗？`)) {
                const toolName = mcpTools[index].name;
                mcpTools.splice(index, 1);
                saveMcpTools();
                renderMcpTools();
                log(`已删除工具: ${toolName}`, 'info');
            }
        };

        // 保存工具
        function saveMcpTools() {
            localStorage.setItem('mcpTools', JSON.stringify(mcpTools));
        }

        // 获取工具列表
        function getMcpTools() {
            return mcpTools.map(tool => ({
                name: tool.name,
                description: tool.description,
                inputSchema: tool.inputSchema
            }));
        }

        // 执行工具调用
        function executeMcpTool(toolName, toolArgs) {
            const tool = mcpTools.find(t => t.name === toolName);

            if (!tool) {
                log(`未找到工具: ${toolName}`, 'error');
                return {
                    success: false,
                    error: `未知工具: ${toolName}`
                };
            }

            // 如果有模拟返回结果，使用它
            if (tool.mockResponse) {
                // 替换模板变量
                let responseStr = JSON.stringify(tool.mockResponse);

                // 替换 ${paramName} 格式的变量
                if (toolArgs) {
                    Object.keys(toolArgs).forEach(key => {
                        const regex = new RegExp(`\\$\\{${key}\\}`, 'g');
                        responseStr = responseStr.replace(regex, toolArgs[key]);
                    });
                }

                try {
                    const response = JSON.parse(responseStr);
                    log(`工具 ${toolName} 执行成功，返回模拟结果: ${responseStr}`, 'success');
                    return response;
                } catch (e) {
                    log(`解析模拟返回结果失败: ${e.message}`, 'error');
                    return tool.mockResponse;
                }
            }

            // 没有模拟返回结果，返回默认成功消息
            log(`工具 ${toolName} 执行成功，返回默认结果`, 'success');
            return {
                success: true,
                message: `工具 ${toolName} 执行成功`,
                tool: toolName,
                arguments: toolArgs
            };
        }

        initApp();
        initMcpTools();
    </script>
</body>

</html>